package com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.util;

import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.scanners.Scanner;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.scanners.SubTypesScanner;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.scanners.TypeAnnotationsScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.Configuration;
import com.ruoyi.flow.common.tools.mediaconverter.converter.reflections.ReflectionsException;

public class ConfigurationBuilder implements Configuration {
	private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationBuilder.class);

	private Set<Scanner> scanners;
	private Set<URL> urls;
	private Predicate<String> inputsFilter;
	private ExecutorService executorService;
	private ClassLoader[] classLoaders;

	public ConfigurationBuilder() {
		scanners = Sets.<Scanner>newHashSet(new TypeAnnotationsScanner(), new SubTypesScanner());
		urls = Sets.newHashSet();
	}

	/**
	 * 通过包名获取该包下的相关类型。
	 * 
	 * @param packages
	 *            包路径，数组。例如{@code com.nestplat.mediaconverter}
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static ConfigurationBuilder build(final Object... packages) {

		ConfigurationBuilder builder = new ConfigurationBuilder();

		// flatten
		List<Object> parameters = Lists.newArrayList();
		if (packages != null) {
			for (Object param : packages) {
				if (param != null) {
					if (param.getClass().isArray()) {
						for (Object p : (Object[]) param)
							if (p != null)
								parameters.add(p);
					} else if (param instanceof Iterable) {
						for (Object p : (Iterable<Object>) param)
							if (p != null)
								parameters.add(p);
					} else
						parameters.add(param);
				}
			}
		}

		List<ClassLoader> loaders = Lists.newArrayList();
		for (Object param : parameters)
			if (param instanceof ClassLoader)
				loaders.add((ClassLoader) param);

		ClassLoader[] classLoaders = loaders.isEmpty() ? null : loaders.toArray(new ClassLoader[loaders.size()]);
		FilterBuilder filter = new FilterBuilder();
		List<Scanner> scanners = Lists.newArrayList();

		for (Object param : parameters) {
			if (param instanceof String) {
				builder.addUrls(ClasspathHelper.forPackage((String) param, classLoaders));
				filter.includePackage((String) param);
			} else if (param instanceof Class) {
				if (Scanner.class.isAssignableFrom((Class) param)) {
					try {
						builder.addScanners(((Scanner) ((Class) param).newInstance()));
					} catch (Exception e) {
						/* fallback */ }
				}
				builder.addUrls(ClasspathHelper.forClass((Class) param, classLoaders));
				filter.includePackage(((Class) param));
			} else if (param instanceof Scanner) {
				scanners.add((Scanner) param);
			} else if (param instanceof URL) {
				builder.addUrls((URL) param);
			} else if (param instanceof ClassLoader) {
				/* already taken care */ } else if (param instanceof Predicate) {
				filter.add((Predicate<String>) param);
			} else if (param instanceof ExecutorService) {
				builder.setExecutorService((ExecutorService) param);
			} else {
				LOGGER.error("could not use param {}", param, new ReflectionsException("could not use param " + param));
			}
		}

		if (builder.getUrls().isEmpty()) {
			if (classLoaders != null) {
				builder.addUrls(ClasspathHelper.forClassLoader(classLoaders)); // default urls getResources("")
			} else {
				builder.addUrls(ClasspathHelper.forClassLoader()); // default urls getResources("")
			}
		}

		builder.filterInputsBy(filter);
		if (!scanners.isEmpty()) {
			builder.setScanners(scanners.toArray(new Scanner[scanners.size()]));
		}
		if (!loaders.isEmpty()) {
			builder.addClassLoaders(loaders);
		}

		return builder;
	}

	public ConfigurationBuilder forPackages(String... packages) {
		for (String pkg : packages) {
			addUrls(ClasspathHelper.forPackage(pkg));
		}
		return this;
	}

	public Set<Scanner> getScanners() {
		return scanners;
	}

	/**
	 * set the scanners instances for scanning different metadata
	 */
	public ConfigurationBuilder setScanners(final Scanner... scanners) {
		this.scanners.clear();
		return addScanners(scanners);
	}

	/**
	 * set the scanners instances for scanning different metadata
	 */
	public ConfigurationBuilder addScanners(final Scanner... scanners) {
		this.scanners.addAll(Sets.newHashSet(scanners));
		return this;
	}

	public Set<URL> getUrls() {
		return urls;
	}

	/**
	 * set the urls to be scanned
	 * <p>
	 * use
	 * {@link ClasspathHelper}
	 * convenient methods to get the relevant urls
	 */
	public ConfigurationBuilder setUrls(final Collection<URL> urls) {
		this.urls = Sets.newHashSet(urls);
		return this;
	}

	/**
	 * set the urls to be scanned
	 * <p>
	 * use
	 * {@link ClasspathHelper}
	 * convenient methods to get the relevant urls
	 */
	public ConfigurationBuilder setUrls(final URL... urls) {
		this.urls = Sets.newHashSet(urls);
		return this;
	}

	/**
	 * add urls to be scanned
	 * <p>
	 * use
	 * {@link ClasspathHelper}
	 * convenient methods to get the relevant urls
	 */
	public ConfigurationBuilder addUrls(final Collection<URL> urls) {
		this.urls.addAll(urls);
		return this;
	}

	/**
	 * add urls to be scanned
	 * <p>
	 * use
	 * {@link ClasspathHelper}
	 * convenient methods to get the relevant urls
	 */
	public ConfigurationBuilder addUrls(final URL... urls) {
		this.urls.addAll(Sets.newHashSet(urls));
		return this;
	}

	public Predicate<String> getInputsFilter() {
		return inputsFilter;
	}

	/**
	 * sets the input filter for all resources to be scanned.
	 * <p>
	 * supply a {@link Predicate} or use the {@link FilterBuilder}
	 */
	public void setInputsFilter(Predicate<String> inputsFilter) {
		this.inputsFilter = inputsFilter;
	}

	/**
	 * sets the input filter for all resources to be scanned.
	 * <p>
	 * supply a {@link Predicate} or use the {@link FilterBuilder}
	 */
	public ConfigurationBuilder filterInputsBy(Predicate<String> inputsFilter) {
		this.inputsFilter = inputsFilter;
		return this;
	}

	public ExecutorService getExecutorService() {
		return executorService;
	}

	/**
	 * sets the executor service used for scanning.
	 */
	public ConfigurationBuilder setExecutorService(ExecutorService executorService) {
		this.executorService = executorService;
		return this;
	}

	/**
	 * sets the executor service used for scanning to ThreadPoolExecutor with core
	 * size as {@link Runtime#availableProcessors()}
	 * <p>
	 * default is ThreadPoolExecutor with a single core
	 */
	public ConfigurationBuilder useParallelExecutor() {
		return useParallelExecutor(Runtime.getRuntime().availableProcessors());
	}

	/**
	 * sets the executor service used for scanning to ThreadPoolExecutor with core
	 * size as the given availableProcessors parameter. the executor service spawns
	 * daemon threads by default.
	 * <p>
	 * default is ThreadPoolExecutor with a single core
	 */
	public ConfigurationBuilder useParallelExecutor(final int availableProcessors) {
		ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true)
				.setNameFormat("com.ruoyi.flow.common.tools.mediaconverter.converter.reflections-scanner-%d").build();
		setExecutorService(Executors.newFixedThreadPool(availableProcessors, factory));
		return this;
	}

	/**
	 * get class loader, might be used for scanning or resolving methods/fields
	 */
	public ClassLoader[] getClassLoaders() {
		return classLoaders;
	}

	/**
	 * set class loader, might be used for resolving methods/fields
	 */
	public void setClassLoaders(ClassLoader[] classLoaders) {
		this.classLoaders = classLoaders;
	}

	/**
	 * add class loader, might be used for resolving methods/fields
	 */
	public ConfigurationBuilder addClassLoader(ClassLoader classLoader) {
		return addClassLoaders(classLoader);
	}

	/**
	 * add class loader, might be used for resolving methods/fields
	 */
	public ConfigurationBuilder addClassLoaders(ClassLoader... classLoaders) {
		this.classLoaders = this.classLoaders == null ? classLoaders
				: ObjectArrays.concat(this.classLoaders, classLoaders, ClassLoader.class);
		return this;
	}

	/**
	 * add class loader, might be used for resolving methods/fields
	 */
	public ConfigurationBuilder addClassLoaders(Collection<ClassLoader> classLoaders) {
		return addClassLoaders(classLoaders.toArray(new ClassLoader[classLoaders.size()]));
	}
}
